package phases

import (
	"fmt"
	"strings"

	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkeinit "gopkg.openfuyao.cn/bkecommon/cluster/initialize"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	ctrl "sigs.k8s.io/controller-runtime"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/command"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/condition"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
)

const (
	EnsureLoadBalanceName confv1beta1.BKEClusterPhase = "EnsureLoadBalance"
)

type EnsureLoadBalance struct {
	phaseframe.BasePhase
}

func NewEnsureLoadBalance(ctx *phaseframe.PhaseContext) phaseframe.Phase {
	base := phaseframe.NewBasePhase(ctx, EnsureLoadBalanceName)
	return &EnsureLoadBalance{base}
}

func (e *EnsureLoadBalance) Execute() (_ ctrl.Result, err error) {
	if err = e.ConfiguringLoadBalancer(); err != nil {
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}

func (e *EnsureLoadBalance) NeedExecute(old *bkev1beta1.BKECluster, new *bkev1beta1.BKECluster) (needExecute bool) {
	if !e.BasePhase.DefaultNeedExecute(old, new) {
		return false
	}
	// 如果Status.Ready为false，需要配置负载均衡器
	if !new.Status.Ready {
		e.SetStatus(bkev1beta1.PhaseWaiting)
		return true
	}

	haveLoadBalance := true
	// 如果spec.ControlPlaneEndpoint.Host为master节点，不需要配置负载均衡器
	for _, node := range new.Spec.ClusterConfig.Nodes {
		if node.IP == new.Spec.ControlPlaneEndpoint.Host {
			haveLoadBalance = false
			break
		}
	}

	if !haveLoadBalance {
		return false
	}

	// 如果有master节点变更，需要配置负载均衡器
	nodes := phaseutil.GetNeedLoadBalanceNodes(new)
	if len(nodes) == 0 {
		return false
	}

	e.SetStatus(bkev1beta1.PhaseWaiting)
	return true
}

// ConfiguringLoadBalancer configures a load balancer for the control plane endpoint.
func (e *EnsureLoadBalance) ConfiguringLoadBalancer() (err error) {
	ctx, c, bkeCluster, scheme, log := e.Ctx.Untie()

	nodes := bkenode.Nodes(bkeCluster.Spec.ClusterConfig.Nodes).Master()

	if len(nodes) == 0 {
		log.Warn("ConfigureLoadBalancer", "no master nodes found")
		return errors.New("no master nodes found")
	}

	errs := []string{}
	for _, node := range nodes {
		if !bkeCluster.GetNodeStateFlag(node.IP, bkev1beta1.NodeEnvFlag) {
			errs = append(errs, fmt.Sprintf("master node %s agent is not ready,cannot configure load balancer", node.IP))
		}
	}
	if len(errs) > 0 {
		log.Warn("ConfigureLoadBalancer", strings.Join(errs, ","))
		return errors.New(strings.Join(errs, ","))
	}

	// step 2 check control plane endpoint is an external load balancer
	if clusterutil.AvailableLoadBalancerEndPoint(bkeCluster.Spec.ControlPlaneEndpoint, bkeCluster.Spec.ClusterConfig.Nodes) {
		log.Debug("ControlPlaneEndpoint is a external load balancer")

		log.Info("ConfigureLoadBalancer", "Start to configure load balancer for control plane endpoint")
		cfg := bkeinit.BkeConfig(*bkeCluster.Spec.ClusterConfig)
		loadBalanceCommand := command.HA{
			BaseCommand: command.BaseCommand{
				Ctx:         ctx,
				NameSpace:   bkeCluster.Namespace,
				Client:      c,
				Scheme:      scheme,
				OwnerObj:    bkeCluster,
				ClusterName: bkeCluster.Name,
				Unique:      true,
			},
			MasterNodes:              nodes.Master(),
			ControlPlaneEndpointPort: bkeCluster.Spec.ControlPlaneEndpoint.Port,
			ControlPlaneEndpointVIP:  bkeCluster.Spec.ControlPlaneEndpoint.Host,
			ImageRepo:                cfg.ImageRepo(),
			ManifestsDir:             cfg.Cluster.Kubelet.ManifestsDir,
			VirtualRouterId:          cfg.CustomExtra["masterVirtualRouterId"],
		}
		// step 3 configure load balancer, and new HA command
		log.Debug("step 3 configure load balancer, and new HA command")
		if err := loadBalanceCommand.New(); err != nil {
			errInfo := "failed to create k8s HA Command"
			log.Warn("ConfigureLoadBalancer", "%s: %v", errInfo, err)
			condition.ConditionMark(bkeCluster, bkev1beta1.ControlPlaneEndPointSetCondition, confv1beta1.ConditionFalse, constant.CommandCreateFailedReason, errInfo)
			return nil
		}
		log.Info(constant.LoadBalancerCreatingReason, "Waiting load balancer configured ready")

		err, successNodes, failedNodes := loadBalanceCommand.Wait()
		if err != nil {
			errInfo := "failed to configure load balancer"
			log.Warn(constant.LoadBalancerNotReadyReason, "%s: %v", errInfo, err)
			return errors.Errorf("failed to configure load balancer: %v", err)
		}

		for _, node := range failedNodes {
			nodeIP := phaseutil.GetNodeIPFromCommandWaitResult(node)
			bkeCluster.SetNodeStateWithMessage(nodeIP, bkev1beta1.NodeInitFailed, "Failed to configure load balancer")
		}

		for _, node := range successNodes {
			nodeIP := phaseutil.GetNodeIPFromCommandWaitResult(node)
			bkeCluster.SetNodeStateWithMessage(nodeIP, bkev1beta1.NodeInitializing, "Load balancer configured")
			// ping agent success，mask agentFlag
			bkeCluster.MarkNodeStateFlag(nodeIP, bkev1beta1.NodeHAFlag)
		}

		if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
			return err
		}

		if len(failedNodes) > 0 {
			log.Warn(constant.LoadBalancerNotReadyReason, "failed to configure load balancer: %v", err)
			log.Warn(constant.LoadBalancerNotReadyReason, "The load balancer configured failed on the following Nodes %v", failedNodes, ",")
			commandErrs, err := phaseutil.LogCommandFailed(*loadBalanceCommand.Command, failedNodes, log, constant.LoadBalancerNotReadyReason)
			phaseutil.MarkNodeStatusByCommandErrs(bkeCluster, commandErrs)
			log.Warn(constant.LoadBalancerNotReadyReason,
				"Load balancer configured failed, you can check the BKEAgent log on the error node (/var/log/bkeagent.log) and manually resolve the problem. Then restart the BKEAgent on the node")
			return errors.Errorf("failed to configure load balancer, loadBalanceCommand run failed in flow nodes: %v, err: %v", strings.Join(failedNodes, ","), err)
		}

		endpoint := bkeCluster.Spec.ControlPlaneEndpoint.String()
		log.Info(constant.LoadBalancerReadyReason, "The load balancer was configured on the following Nodes %v", successNodes)
		log.Info(constant.LoadBalancerReadyReason, "The cluster %q control plane endpoint will be %q", bkeCluster.Name, endpoint)
		condition.ConditionMark(bkeCluster, bkev1beta1.ControlPlaneEndPointSetCondition, confv1beta1.ConditionTrue, constant.LoadBalancerReadyReason, "")
		bkeCluster.Status.Ready = true

	} else {
		log.Debug("ControlPlaneEndpoint is a internal point")
		condition.ConditionMark(bkeCluster, bkev1beta1.ControlPlaneEndPointSetCondition, confv1beta1.ConditionTrue, constant.LoadBalancerNotConfiguredReason, "load balancer not configured")
		log.Warn(constant.LoadBalancerNotReadyReason, "Without load balancer configured, the cluster %q control plane endpoint is %q", bkeCluster.Name, bkeCluster.Spec.ControlPlaneEndpoint.String())
		bkeCluster.Status.Ready = true
	}

	if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
		return err
	}
	log.Debug("reconcile load balance success")
	return nil
}
